﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WiiGeeDotNet.events;
using System.Xml.Linq;
using System.Globalization;
using WiiGeeDotNet;
using WiiGeeDotNET;

namespace WiiGeeDotNet.logic
{
  /** 
   * This Class units a Quantizer-Component and an Model-Component.
   * In this implementation a k-mean-algorithm for quantization and
   * a hidden markov model as instance for the model has been used.
   * 
   * @author Benjamin 'BePo' Poppinga
   */
  public class GestureModel
  {

    /** The number of states the hidden markov model consists of */
    public int NumStates
    {
      set;
      get;
    }

    /** The number of observations for the hmm and k-mean */
    public int NumObservations
    {
      get;
      set;
    }

    /** The id representation of this model */
    public int Id
    {
      set;
      get;
    }

    /** The quantization component */
    public Quantizer Quantizer
    {
      set;
      get;
    }

    /** The statistical model, hidden markov model */
    public HMM Markovmodell
    {
      set;
      get;
    }

    /** The default probability of this gesturemodel,
     * needed for the bayes classifier */
    private double m_defaultprobability;

    public String Name
    {
      get;
      set;
    }

    NumberFormatInfo m_formatProvider;

    /// <summary>
    /// Creates a Unit (Quantizer&Model).
    /// </summary>
    /// <param name="id">representation of a gesture "name"/class.</param>
    public GestureModel(int id)
    {
      this.Id = id;
      this.NumStates = 8; // n=8 states empirical value
      this.NumObservations = 14; // k=14 observations empirical value
      this.Markovmodell = new HMM(NumStates, NumObservations); // init model
      this.Quantizer = new Quantizer(NumStates); // init quantizer

      m_formatProvider = new NumberFormatInfo();
      m_formatProvider.NumberGroupSeparator = ".";
    }

    public GestureModel(int _id, String _gestureData):this(_id)
    {
      XDocument xml = XDocument.Parse(_gestureData);
      var gestures = from gesture in xml.Descendants("Gesture")
                     select new
                     {
                       Name = gesture.Element("Name").Value,
                       NumStates = gesture.Element("NumStates").Value,
                       NumObservations = gesture.Element("NumObservations").Value,
                       DefaultProbability = gesture.Element("DefaultProbability").Value,
                       QantizerRadius = gesture.Element("Qantizer").Element("Radius").Value,
                       QantizerMap = gesture.Element("Qantizer").Element("Map").Value,
                       MarkovPi = gesture.Element("Markov").Element("Pi").Value,
                       MarkovA = gesture.Element("Markov").Element("A").Value,
                       MarkovB = gesture.Element("Markov").Element("B").Value,
                     };
      var g = gestures.First();

      this.Id = _id;
      this.Name = g.Name;
      
      this.Quantizer = new Quantizer(Int32.Parse(g.NumStates));
      this.Quantizer.SetUpManually(Util.ParseMultiDoubleArray(g.QantizerMap, m_formatProvider), 
                                   Double.Parse(g.QantizerRadius, m_formatProvider));

      this.SetDefaultProbability(Double.Parse(g.DefaultProbability, m_formatProvider));

      this.Markovmodell = new HMM(Int32.Parse(g.NumStates), Int32.Parse(g.NumObservations));
      this.Markovmodell.setPi(Util.ParseDoubleArray(g.MarkovPi, m_formatProvider));
      this.Markovmodell.setA(Util.ParseMultiDoubleArray(g.MarkovA, m_formatProvider));
      this.Markovmodell.setB(Util.ParseMultiDoubleArray(g.MarkovB, m_formatProvider));

    }

    /// <summary>
    /// Trains the model to a set of motion-sequences, representing different evaluations of a gesture
    /// </summary>
    /// <param name="trainsequence">a vector of gestures</param>
    public void Train(List<Gesture> trainsequence)
    {
      // summarize all vectors from the different gestures in one
      // gesture called sum.
      double maxacc = 0;
      double minacc = 0;
      Gesture sum = new Gesture();

      for (int i = 0; i < trainsequence.Count; i++)
      {
        List<AccelerationEvent> t = trainsequence[i].GetData();

        // add the max and min acceleration, we later get the average
        maxacc += trainsequence[i].GetMaxAcceleration();
        minacc += trainsequence[i].GetMinAcceleration();

        // transfer every single accelerationevent of each gesture to
        // the new gesture sum
        for (int j = 0; j < trainsequence[i].GetData().Count; j++)
        {
          sum.Add(t[j]);
        }

      }

      // get the average and set it to the sum gesture
      sum.SetMaxAndMinAcceleration(maxacc / trainsequence.Count, minacc / trainsequence.Count);

      // train the centeroids of the quantizer with this master gesture sum.
      this.Quantizer.TrainCenteroids(sum);

      // convert gesture vector to a sequence of discrete values
      List<int[]> seqs = new List<int[]>();
      for (int i = 0; i < trainsequence.Count; i++)
      {
        seqs.Add(this.Quantizer.GetObservationSequence(trainsequence[i]));
      }

      // train the markov model with this derived discrete sequences
      this.Markovmodell.train(seqs);

      // set the default probability for use with the bayes classifier
      this.SetDefaultProbability(trainsequence);
    }

    /// <summary>
    /// Returns the probability that a gesture matches to this gesture model.
    /// </summary>
    /// <param name="gesture">a gesture to test.</param>
    /// <returns>probability that the gesture belongs to this gesture</returns>
    public double Matches(Gesture gesture)
    {
      int[] sequence = Quantizer.GetObservationSequence(gesture);
      return this.Markovmodell.getProbability(sequence);
    }

    /// <summary>
    /// For debug purposes or very technical interested people. :)
    /// </summary>
    public void PrintMap()
    {
      Console.WriteLine("Gesture " + this.Id + " Quantizer-Map:");
      this.Quantizer.PrintMap();
    }

    /// <summary>
    /// For debug purposes or very technical interested people. :)
    /// </summary>
    public void Print()
    {
      Console.WriteLine("Debug Output for Gesture " + this.Id);
      Console.WriteLine("HMM-Print:");
      this.Markovmodell.print();
      Console.WriteLine("Quanzizer-Print:");
      this.Quantizer.PrintMap();
    }


    /// <summary>
    /// Returns the model probability for bayes.
    /// </summary>
    /// <returns>model probability</returns>
    public double GetDefaultProbability()
    {
      return this.m_defaultprobability;
    }

    public void SetDefaultProbability(double prob)
    {
      this.m_defaultprobability = prob;
      Console.WriteLine("def-prob. set to = " + this.m_defaultprobability);
    }

    /// <summary>
    /// Since the bayes classifier needs a model probability for
    /// each model this has to be set once after training. As model
    /// probability the average probability value has been choosen.
    /// TODO: try lowest or highest model probability as alternative
    /// </summary>
    /// <param name="defsequence">the vector of training sequences</param>
    private void SetDefaultProbability(List<Gesture> defsequence)
    {
      double prob = 0;
      for (int i = 0; i < defsequence.Count; i++)
      {
        prob += this.Matches(defsequence[i]);
      }

      this.m_defaultprobability = (prob) / defsequence.Count;
    }

    public String GetGestureData()
    {
      XElement xml = new XElement("Gesture");
      xml.Add(new XElement("Name", this.Name));
      xml.Add(new XElement("NumStates", this.NumStates.ToString()));
      xml.Add(new XElement("NumObservations", this.NumObservations.ToString()));
      xml.Add(new XElement("DefaultProbability", this.GetDefaultProbability().ToString(m_formatProvider)));
      xml.Add(new XElement("Qantizer",
                new XElement("Radius", this.Quantizer.GetRadius().ToString(m_formatProvider)),
                new XElement("Map", Util.BuildArrayString(this.Quantizer.GetHashMap(), m_formatProvider))));
      xml.Add(new XElement("Markov",
                new XElement("Pi", Util.BuildArrayString(this.Markovmodell.getPi(), m_formatProvider)),
                new XElement("A", Util.BuildArrayString(this.Markovmodell.getA(), m_formatProvider)),
                new XElement("B", Util.BuildArrayString(this.Markovmodell.getB(), m_formatProvider))));
      return xml.ToString();
    }
  }
}
